
from pinhole.datasource.document import Document
from pinhole.datasource.summary import Summary, Outline
from pinhole.datasource.publication import Publication
from pinhole.project import Project
from pinhole.user import AuthRequest

from fastapi import FastAPI, Request, Response, status
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError

from typing import List
from tempfile import mktemp
from shutil import copy
from typing import Optional, Any
from os import environ, remove
from os.path import isdir

import base64


app = FastAPI()
project_path = environ['PINHOLE_PROJECT']
project = Project.loadf(project_path) if isdir(project_path) else Project.create(project_path)


@app.middleware('http')
async def authentication_middleware(request: Request, call_next):
    authorized = False

    if request.client is not None and request.client.host == '127.0.0.1':
        authorized = True
    else:
        try:
            encoded_email, encoded_pwd = request.headers.get('Authentication', '').split(':')
            email = base64.b64decode(encoded_email).decode('utf-8')
            pwd = base64.b64decode(encoded_pwd).decode('utf-8')
            if pwd == project.admin_password and email == project.admin_email:
                authorized = True
        except Exception as ex:
            pass

    if authorized:
        return await call_next(request)
    else:
        return JSONResponse(
            {
                "succeeded": False,
                "message": "authentication failure"
            },
            status_code=403
        )


@app.get("/")
async def root():
    return {
        "succeeded": True,
        "message": "no method specified"
    }


@app.post("/user/get")
async def get_user_ref(request: AuthRequest):
    return {
        "succeeded": True,
        "user": project.get_user_ref(request.email, request.password)
    }


@app.post("/document/create")
async def create_document(document: Document):
    project.create_document(document)
    return {"succeeded": True}


@app.get("/document/get")
async def get_document(id: int):
    return {
        "succeeded": True,
        "document": project.get_document(id)
    }


@app.get("/document/list")
async def list_document():
    return {
        "succeeded": True,
        "documents": project.get_document_refs()
    }


@app.post("/outline/create")
async def create_outline(outline: Outline):
    project.create_outline(outline)
    return {"succeeded": True}


@app.get("/outline/list")
async def list_outline(
    document_ids: Optional[str] = None,
    publication_ids: Optional[str] = None
):
    if document_ids is not None:
        assert publication_ids is None, f"cannot query by document_ids and publication_ids at the same time"
        document_ids = document_ids.strip()
        if document_ids == "":
            outlines: Any = dict()
        else:
            outlines = project.get_outlines_of_documents([int(s) for s in document_ids.split(',')])
    elif publication_ids is not None:
        publication_ids = publication_ids.strip()
        if publication_ids == "":
            outlines = dict()
        else:
            outlines = project.get_outlines_of_publications([int(s) for s in publication_ids.split(',')])
    else:
        outlines = project.get_outlines()

    return {
        "succeeded": True,
        "outlines": outlines
    }


@app.post("/summary/create")
async def create_summary(summary: Summary):
    project.create_summary(summary)
    return {"succeeded": True}


@app.get("/summary/list")
async def list_summary():
    return {
        "succeeded": True,
        "summaries": project.get_summary_refs()
    }


@app.get("/summary/get")
async def get_summary(summary_id: Optional[int] = None,
                      document_id: Optional[int] = None,
                      publication_id: Optional[int] = None):

    summary: Optional[Summary] = None
    if summary_id is not None:
        summary = project.get_summary(summary_id)
    elif document_id is not None:
        summary = project.get_summary_of_document(document_id)
    elif publication_id is not None:
        summary = project.get_summary_of_publication(publication_id)

    return {"succeeded": True, "summary": summary}


@app.get("/summary/get_embedding")
async def get_summary_embedding(summary_id: int):
    return {"succeeded": True, "embedding": project.get_summary_embedding(summary_id)}


@app.post("/summary/set_embedding")
async def set_summary_embedding(summary_id: int, embedding: List[float]):
    project.set_summary_embedding(summary_id, embedding)
    return {"succeeded": True}


@app.post("/publication/create")
async def create_publication(publication: Publication):
    project.create_publication(publication)
    return {"succeeded": True}


@app.get("/publication/list")
async def list_publication():
    return {
        "succeeded": True,
        "publications": project.get_publication_refs()
    }


@app.get("/publication/get")
async def get_publication(id: int):
    return {
        "succeeded": True,
        "publication": project.get_publication(id)
    }


@app.exception_handler(Exception)
async def exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
    content = {
        "succeeded": False,
        "message": f"{type(exc).__name__}: {exc}"
    }

    return JSONResponse(content=content, status_code=status.HTTP_400_BAD_REQUEST)


@app.get("/download/database")
async def download_database():
    tempfile = mktemp()
    copy(project.database_realpath, tempfile)

    with open(tempfile, 'rb') as ftemp:
        content = ftemp.read()

    remove(tempfile)
    return Response(content, headers={
        'Content-Disposition': f'attachment; filename="database.db"'
    })
